'''
Created on Nov 19, 2010

@author: dexen
'''

import time

from dexen.system.common.transact import SlaveTransact, SlaveRecvTransact
from dexen.system.common.meta import JobMeta, TaskMeta


#===============================================================================
# Constants
#===============================================================================
M_CRASHED = "MASTER CRASHED"
M_RUNNING = "MASTER RUNNING"
M_STOPPED = "STOPPED (BY USER)"
M_FINISHED = "STOPPED (BY MASTER)"

#===============================================================================
# State Classes
#===============================================================================
class NodeState(object):
    def __init__(self, name, addr, master_states=[], slave_states=[]):
        self._name = name
        self._master_states = master_states
        self._slave_states = slave_states
        self._ip = addr.ip
        self._port = addr.port
    
    @property
    def ip(self):
        return self._ip
    
    @property
    def port(self):
        return self._port
    
    @property
    def name(self):
        return self._name
    
    @property
    def master_states(self):
        return self._master_states
    
    @property
    def slave_states(self):
        return self._slave_states


class MasterState(object):
    def __init__(self, task_states, num_inds, slave_data, state):
        self.task_states = task_states
        self.num_inds = num_inds
        self._slave_data = slave_data
        self.state = state
        self.stop_time = None
        self._master_name = ""
        self._job_name = ""
        self._node_name = ""

    @property
    def master_name(self):
        return self._master_name
    
    @master_name.setter
    def master_name(self, value):
        self._master_name = value
    
    @property
    def job_name(self):
        return self._job_name
    
    @job_name.setter
    def job_name(self, value):
        self._job_name = value

    @property
    def node_name(self):
        return self._node_name
    
    @node_name.setter
    def node_name(self, value):
        self._node_name = value
        
    def set_stop_time(self, time):
        self.stop_time = time

    def get_stop_time(self):
        return self.stop_time

    def get_state(self):
        return self.state

    def set_state(self, state):
        self.state = state

    def is_finished(self):
        return self.state == M_FINISHED

    def is_running(self):
        return self.state == M_RUNNING

    def get_task_states(self):
        return self.task_states
    
    def get_num_inds(self):
        return self.num_inds
    
    def get_data(self, name):
        if not self._slave_data.has_key(name):
            return None
        return self._slave_data[name]

    
#===============================================================================
# Slave State
#===============================================================================
S_CRASHED = "SLAVE CRASHED"
S_BUSY = "SLAVE BUSY"
S_IDLE = "SLAVE IDLE"

class SlaveState(object):
    def __init__(self, node_name, name):
        self._node_name = node_name
        self._name = name
        self._state = S_IDLE
    
    @property
    def name(self):
        return self._name
    
    @property
    def state(self):
        return self._state
    
    @property
    def node_name(self):
        return self._node_name
    
    def update(self, transact):
        assert isinstance(transact, SlaveTransact)
        if isinstance(transact, SlaveRecvTransact):
            if transact.has_slave_crashed():
                self._state = S_CRASHED
            else: self._state = S_IDLE
        
    def idle(self):
        self._state = S_IDLE
    
    def busy(self):
        self._state = S_BUSY
    
    def is_crashed(self):
        return self._state == S_CRASHED
    
    def is_busy(self):
        return self._state == S_BUSY
    
    def is_idle(self):
        return self._state == S_IDLE

#===============================================================================
# Job State
#===============================================================================
class JobState(object):
    def __init__(self, job_meta, master_state):
        assert isinstance(job_meta, JobMeta)
        self.job_id = job_meta.get_id()
        self.job_name = job_meta.get_name()
        self.start_time = job_meta.get_start_time()
        self.scores = job_meta.get_scores()
        self.master_state = master_state
        assert isinstance(master_state, MasterState)
        self.state = master_state.get_state()
        self.tot_births = master_state.get_num_inds()
        self.stop_time = master_state.get_stop_time()
    
    def get_id(self):
        return self.job_id
    
    def get_name(self):
        return self.job_name
    
    def get_scores(self):
        return self.scores
    
    def get_header(self):
        out = "|Job ID | Job Name | Start Time |       Status       |\n"
        out += "------------------------------------------------------"
        return out
    
    def is_running(self):
        return self.state == M_RUNNING
    
    def _get_state_str(self):
        if self.state == M_CRASHED:
            return "CRASHED"
        if self.state == M_STOPPED:
            return "STOPPED (BY USER)"
        if self.state == M_FINISHED:
            return "STOPPED (BY MASTER)"
        if self.state == M_RUNNING:
            return "RUNNING"
    
    def _get_time_str(self):
        return time.strftime("%Y/%m/%d-%H:%M", time.localtime(self.start_time))
    
    def _time_to_str(self, _secs):
        t = 60 * 60 * 24
        secs = int(_secs)
        days = secs / t 
        secs = secs - days * t
        hours = secs / 3600
        secs = secs - hours * 3600
        mins = secs / 60
        secs = secs - mins * 60
        out = ""
        if (days > 0):
            out += "%d days " % (days)
        if (hours > 0):
            out += "%d hours " % (hours)
        if (mins > 0):
            out += "%d mins " % (mins)
        out += "%d secs" % (secs)
        return out
    
    def _calc_elapsed_time(self):
        if self.stop_time is not None:
            return self.stop_time - self.start_time
        return time.time() - self.start_time
      
    def get_elapsed_time(self):
        return self._time_to_str(self._calc_elapsed_time())

    def get_start_time_str(self):
        return self._get_time_str()
    
    def get_start_time(self):
        return self.start_time
    
    def get_status(self):
        return self._get_state_str()
    
    def get_avg_time_per_ind(self):
        return self._calc_elapsed_time() / self.tot_births
    
    def get_info(self):
        out = "%s\n" % (" Job Info ".center(40, '='))
        out += "Job id: %d\n" % (self.job_id)
        out += "Job name: %s\n" % (self.job_name)
        out += "Job status: %s\n" % (self._get_state_str())
        out += "Start time: %s\n" % (self._get_time_str())
        elapsed_time = self._calc_elapsed_time()
        out += "Elapsed time: %s\n" % (self._time_to_str(elapsed_time))
        out += "Total births: %d\n" % (self.tot_births)
        out += "Average time per individual: %.2f secs\n" % (elapsed_time / 
                                                           self.tot_births)
        master_state = self.master_state
        assert isinstance(master_state, MasterState)
        temp = ""
        tot_success_execs = 0
        tot_failed_execs = 0
        for task_state in master_state.get_task_states():
            assert isinstance(task_state, TaskState)
            temp += "%s: %d, %d\n" % (task_state.name,
                                    task_state.num_success_execs(),
                                    task_state.num_failed_execs())
            tot_success_execs += task_state.num_success_execs()
            tot_failed_execs += task_state.num_failed_execs()
        out += "Total task executions (success, fail): %d, %d\n" % (
                                                    tot_success_execs,
                                                    tot_failed_execs)
        out += "Breakdown (success, fail):\n"
        out += temp        
        return out

    def get_tasks_info(self):
        result = []
        master_state = self.master_state
        assert isinstance(master_state, MasterState)
        for task_state in master_state.get_task_states():
            assert isinstance(task_state, TaskState)
            result.append((task_state.name,
                           task_state.input_size,
                           task_state.n_pending_inds,
                           task_state.n_processing_inds,
                           task_state.num_success_execs(),
                           task_state.num_failed_execs(),
                           task_state.avg_exec_time))
        return result

    def __repr__(self):
        #c_time = time.strftime("%H:%M:%S",time.localtime(self.creation_time))
        out = "|%s|%s" % (str(self.job_id).center(7), self.job_name.center(10))
        out += "|%s" % (time.strftime("%H:%M:%S",
                                    time.localtime(self.start_time)).center(12))
        out += "|%s|" % (self._get_state_str().center(20))
        return out    
        
#===============================================================================
# TaskState
#===============================================================================
class TaskState(object):
    def __init__(self, meta):
        assert isinstance(meta, TaskMeta)
        self.name = meta.get_name()
        self.label = meta.get_label()
        self.input_size = meta.get_input_size()
        self.n_pending_inds = 0
        self.n_processing_inds = 0
        self.n_successes = 0
        self.n_fails = 0
        self.tot_exec_time = 0.0
    
    def update(self, transact):
        assert isinstance(transact, SlaveRecvTransact)
        self.tot_exec_time += transact.get_exec_time()
        if transact.has_task_succeeded():
            self.n_successes += 1
        elif transact.has_task_failed():
            self.n_fails += 1
    
    def num_success_execs(self):
        return self.n_successes
    
    def num_failed_execs(self):
        return self.n_fails
    
    @property
    def avg_exec_time(self):
        if self.n_successes == 0.0:
            return 0.0
        return self.tot_exec_time / self.n_successes

    def get_header(self):
        return self.label 

    def get_header_tooltip(self):
        return "Pending:Processing"
    
    def get_log_(self):
        out = "%s," % (self.label)
        out += "%s," % (self.n_pending_inds)
        out += "%s," % (self.n_processing_inds)
        out += "%s" % (self.n_successes)
        #out += "%.2f secs" % (self.avg_exec_time)
        return out

    def get_log(self):
        return "%s:%s"%(self.n_pending_inds, self.n_processing_inds)

    def __repr__(self):
        out = "-------------- TaskState ------------\n"
        out += "Name: %s\n" % (self.name)
        out += "Label: %s\n" % (self.label)
        out += "# Pending Inds: %s\n" % (self.n_pending_inds)
        out += "# Processing Inds: %s\n" % (self.n_processing_inds)
        out += "# Successful Execs: %s\n" % (self.n_successes)
        out += "# Failed Execs: %s\n" % (self.n_fails)
        out += "-------------------------------------\n"
        return out

